// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ns

import (
	"fmt"
	"sync"

	"circuit/kit/rh"
)

// UnregulatedFID is the FID interface minus the Walk method.
type UnregulatedFID interface {
	rh.Identifier
	rh.Stater
	rh.Conn
	rh.Traverser
}

//
type UnregulatedWalkFunc func() UnregulatedFID

// RegulateOnce returns an FID representing uFID, which can only be opened and used once.
func RegulateOnce(uFID UnregulatedFID) rh.FID {
	return &regulateOnce{UnregulatedFID: uFID}
}

type regulateOnce struct {
	UnregulatedFID
	o struct {
		sync.Mutex
		opened  bool
		clunked bool
	}
}

func (f *regulateOnce) Open(flag rh.Flag, intr rh.Intr) (err error) {
	f.o.Lock()
	defer f.o.Unlock()
	//
	if f.o.opened {
		return rh.ErrBusy
	}
	if err = f.UnregulatedFID.Open(flag, intr); err != nil {
		return err
	}
	f.o.opened = true
	return nil
}

func (f *regulateOnce) Walk(wname []string) (rh.FID, error) {
	return f, nil
}

func (f *regulateOnce) Clunk() {
	f.o.Lock()
	defer f.o.Unlock()
	//
	f.o.clunked = true
}

//
// E.g. used by the slab ripple help file
func RegulateFork(walk UnregulatedWalkFunc) rh.FID {
	return &regulateFork{
		UnregulatedFID: walk(),
		walk:           walk,
	}
}

type regulateFork struct {
	UnregulatedFID
	walk UnregulatedWalkFunc
}

func (f *regulateFork) Walk(wname []string) (rh.FID, error) {
	return RegulateFork(f.walk), nil
}

//
func RegulateShare(u UnregulatedFID, r Regime) rh.FID {
	stat, err := u.Stat()
	if err != nil {
		panic(err)
	}
	t := &regulateShare{
		regime: r,
		stat:   stat,
		title:  fmt.Sprintf("%s/shared", u.String()),
	}
	t.UnregulatedFID = u
	return t
}

//
type phase byte

const (
	phaseReady = iota
	phaseOpen
	phaseClosed
)

//
type Regime byte

const (
	RegimeNeverClose = iota
	RegimeCloseOnGarbage
)

// regulateShare never closes the underlying file
type regulateShare struct {
	regime Regime
	UnregulatedFID
	o struct {
		sync.Mutex
		phase   phase
		numopen int
	}
	title string
	stat  *rh.Dir
}

func (rs *regulateShare) Q() rh.Q {
	return rs.stat.Q
}

func (rs *regulateShare) String() string {
	return rs.title
}

func (rs *regulateShare) Stat() (*rh.Dir, error) {
	return rs.stat, nil
}

func (rs *regulateShare) Walk(wname []string) (rh.FID, error) {
	return rs, nil
}

//
func (rs *regulateShare) Open(flag rh.Flag, intr rh.Intr) error {
	rs.o.Lock()
	defer rs.o.Unlock()
	//
	switch rs.o.phase {
	case phaseReady:
		if err := rs.UnregulatedFID.Open(flag, intr); err != nil {
			return err
		}
		rs.o.numopen++
		rs.o.phase = phaseOpen
		return nil

	case phaseOpen:
		rs.o.numopen++
		return nil

	case phaseClosed:
		return rh.ErrGone

	default:
		return rh.ErrGone
	}
}

//
func (rs *regulateShare) Clunk() {
	rs.o.Lock()
	defer rs.o.Unlock()
	//
	switch rs.o.phase {
	case phaseReady:
		// nop, clash
	case phaseOpen:
		if rs.o.numopen <= 0 {
			panic("u")
		}
		rs.o.numopen--
		if rs.o.numopen > 0 {
			return
		}
		switch rs.regime {
		case RegimeNeverClose:
			// nop
		case RegimeCloseOnGarbage:
			rs.UnregulatedFID.Clunk()
			rs.o.phase = phaseClosed
		default:
			panic("u")
		}
	case phaseClosed:
		// nop
	}
}

func (rs *regulateShare) Read(offset int64, count int, intr rh.Intr) (rh.Chunk, error) {
	return rs.UnregulatedFID.Read(offset, count, intr)
}

func (rs *regulateShare) Write(offset int64, data rh.Chunk, intr rh.Intr) (n int, err error) {
	return rs.UnregulatedFID.Write(offset, data, intr)
}
